﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.SessionState;
using System.Web.WebSockets;

namespace MJTop_WebSocket
{
    /// <summary>
    /// WebSocketHandler 的摘要说明
    /// </summary>
    public class WebSocketHandler : IHttpHandler, IRequiresSessionState
    {
        private string UsertName { get; set; }

        public void ProcessRequest(HttpContext context)
        {
            if (context.IsWebSocketRequest)
            {
                this.UsertName = context.Request.QueryString["UserName"];
                context.AcceptWebSocketRequest(ProcessChat);
            }
            else
            {
                //非WebSocket请求
                context.Response.Write("非WebSocket请求！");
            }
        }

        private async Task ProcessChat(AspNetWebSocketContext context)
        {
            WebSocket socket = context.WebSocket;
            CancellationToken cancellationToken = new CancellationToken();

            //存储 当前用户与之对应的Socket
            ChatManager.AddUser(UsertName, socket);

            //某人登陆后，给群里其他人发  登陆消息
            await ChatManager.SendMessage(cancellationToken, $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {this.UsertName} 进入聊天室");

            //进入Open状态之后，等待客户端操作（发送消息 或 关闭连接）
            while (socket.State == WebSocketState.Open)
            {
                //缓冲区对象 接收发送的数据信息
                ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
                //等待 当前Socket 发送的 接收消息
                WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, cancellationToken);

                //如果输入帧为取消帧，发送close命令
                if (result.MessageType == WebSocketMessageType.Close) 
                {
                    //移除用户以及与之对应的Socket  因为直接关浏览器会导致CloseAsync异常
                    ChatManager.RemoveUser(UsertName);
                    //群发通知 离开聊天室
                    await ChatManager.SendMessage(cancellationToken, $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {this.UsertName} 离开聊天室");
                    //关闭Socket
                    await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, cancellationToken);
                }
                else if(result.MessageType== WebSocketMessageType.Text)////获取字符串,发送消息
                {
                    string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
                    string content = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {this.UsertName} 发送了：{userMsg}";
                    await ChatManager.SendMessage(cancellationToken, content);
                }
                else
                {
                    //接收的是二进制数据

                }
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }


    public class ChatManager
    {
        private static ConcurrentDictionary<string, WebSocket> _UserDictionary = new ConcurrentDictionary<string, WebSocket>();

        public static void AddUser(string name, WebSocket socket)
        {
            _UserDictionary[name] = socket;
        }
        public static void RemoveUser(string name)
        {
            WebSocket socket = null;

            _UserDictionary.TryRemove(name, out socket);
        }

        /// <summary>
        /// 对每一个Socket 群发消息
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public static async Task SendMessage(CancellationToken cancellationToken, string content)
        {
            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
            buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(content));
            foreach (var socket in _UserDictionary.Select(d => d.Value))
            {
                await socket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken);
            }
        }
    }
}